// 实现数据管理模块

/*用于连接数据库并管理用户的数据*/
#ifndef __DB__HPP__
#define __DB__HPP__

#include "util.hpp"
#include "Log.hpp"
#include <mutex>
#include <cassert>

class user_table
{
public:
    user_table(const std::string &host, const std::string &username,
               const std::string &password, const std::string &dbname,
               unsigned int port = 3306)
    {
        _mysql = mysql_util::mysql_create(host, username, password, dbname, port);
        assert(_mysql != nullptr);
    }

    ~user_table()
    {
        mysql_util::mysql_destory(_mysql);
        _mysql = nullptr;
    }

    /*用户注册 - 往数据库中添加信息 , 用户提供用户信息(用户名和密码)*/
    bool Register(Json::Value &regdata)
    {
/*default usr data ,  password 加密*/
#define INSERT_DATA "insert into  user values(null,'%s',password('%s'),1000,0,0);"

        /*避免无效数据*/
        if (regdata["username"].isNull() || regdata["password"].isNull())
        {
            DBGLOG("please input username or password !");
            return false;
        }

        char strbuff[4096] = {0};
        sprintf(strbuff, INSERT_DATA, regdata["username"].asCString(), regdata["password"].asCString());

        /*加锁 , 保证多线程安全 !*/
        {
            std::unique_lock<std::mutex> lock(_mutex); // 出了作用域自动销毁
            bool ret = mysql_util::SqlExecute(_mysql, strbuff);
            if (ret == false)
            {
                ERRLOG("SQL INSERT ERROR !");
                return false;
            }
        }

        return true;
    }

    /*用户登录 - 验证数据库信息并返回详细用户信息*/
    bool Login(Json::Value &userdata)
    {
/*查询通过用户名和密码查询*/
#define SELECT_DATA "select id, score, total_count, win_count \
                                from user where username='%s' and password=password('%s');"

        /*登录验证 , 就是查询数据库中的信息 , 避免无效数据*/
        if (userdata["username"].isNull() || userdata["password"].isNull())
        {
            DBGLOG("please input username or password !");
            return false;
        }
        char selectbuf[4096] = {0};
        sprintf(selectbuf, SELECT_DATA, userdata["username"].asCString(), userdata["password"].asCString());

        /*这里加锁 , 因为多线程下可能线程不安全 !*/
        MYSQL_RES *result = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mutex); // 出了作用域自动销毁

            bool ret = mysql_util::SqlExecute(_mysql, selectbuf);
            if (ret == false)
            {
                FALOG("SQL SELECT EXECUTE ERROR !");
                return false;
            }
            // select 查询
            /*1. 将数据库查询结果集数据保存本地*/
            result = mysql_store_result(_mysql);
            if (result == nullptr)
            {
                ERRLOG("store_result to loacl error !");
                return false;
            }
        }

        /*2. 获取行, MYSQL_RES指向的是一个 char** 二维数组 , 获取一个用户的完整信息 */
        unsigned long long rows = mysql_num_rows(result);
        /*一个用户的数据是唯一的 , 所以只有一行*/
        if (rows != 1)
        {
            DBGLOG("用户数据不唯一!");
            return false;
        }
        /*3. 获取一个用户的完整信息 , 即获取一个用户一行的数据 , id,score,to.....  */
        MYSQL_ROW info = mysql_fetch_row(result);
        /*4. 返回详细内容 , 将用户信息带出去 , 外部传参会直接存储到外部定义的用户数据类型中*/
        userdata["id"] = (Json::UInt64)std::stol(info[0]);
        userdata["score"] = std::stoi(info[1]);
        userdata["total_count"] = std::stoi(info[2]);
        userdata["win_count"] = std::stoi(info[3]);

        /*5. 释放结果集 , 防止内存泄漏*/
        mysql_free_result(result);

        return true;
    }

    /*通过用户名获取用户信息 -- 用于验证*/
    bool ObtainInfoByName(const std::string &name, Json::Value &userdata)
    {
#define SELECT_BY_NAME "select id , score , total_count , \
                                win_count from user where username='%s';"

        char selectbuff[4096] = {0};
        sprintf(selectbuff, SELECT_BY_NAME, name.c_str());

        /*这里加锁 , 因为多线程下可能线程不安全 !*/
        MYSQL_RES *result = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mutex); // 出了作用域自动销毁

            bool ret = mysql_util::SqlExecute(_mysql, selectbuff);
            if (ret == false)
            {
                FALOG("SQL SELECT EXECUTE ERROR !");
                return false;
            }

            /*1. 将查询结果集保存到本地*/
            result = mysql_store_result(_mysql);
            if (result == nullptr)
            {
                ERRLOG("store_result to loacl error !");
                return false;
            }
        }

        /*2. 获取行数 , 一个用户只能有唯一信息*/
        unsigned long long rows = mysql_num_rows(result);
        if (rows != 1)
        {
            ERRLOG("用户数据不唯一!");
            return false;
        }
        /*3. 获取完整信息*/
        MYSQL_ROW info = mysql_fetch_row(result);

        userdata["id"] = (Json::UInt64)std::stol(info[0]);
        userdata["username"] = name;
        userdata["score"] = std::stoi(info[1]);
        userdata["total_count"] = std::stoi(info[2]);
        userdata["win_count"] = std::stoi(info[3]);

        /*4. 释放结果集 , 防止内存泄漏*/
        mysql_free_result(result);

        return true;
    }

    /*通过用户ID获取用户信息 -- 用于验证*/
    bool ObtainInfoByID(const uint64_t id, Json::Value &userdata)
    {
#define SELECT_BY_ID "select username , score , total_count , \
                                win_count from user where id='%lu';"

        char selectbuff[4096] = {0};
        sprintf(selectbuff, SELECT_BY_ID, id);

        /*这里加锁 , 因为多线程下可能线程不安全 !*/
        MYSQL_RES *result = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mutex); // 出了作用域自动销毁

            bool ret = mysql_util::SqlExecute(_mysql, selectbuff);
            if (ret == false)
            {
                FALOG("SQL SELECT EXECUTE ERROR !");
                return false;
            }

            /*1. 将查询结果集保存到本地*/
            result = mysql_store_result(_mysql);
            if (result == nullptr)
            {
                ERRLOG("store_result to loacl error !");
                return false;
            }
        }

        /*2. 获取行数 , 一个用户只能有唯一信息*/
        unsigned long long rows = mysql_num_rows(result);
        if (rows != 1)
        {
            DBGLOG("用户数据不唯一!");
            return false;
        }
        /*3. 获取完整信息*/
        MYSQL_ROW info = mysql_fetch_row(result);
        userdata["username"] = info[0];
        userdata["score"] = std::stoi(info[1]);
        userdata["total_count"] = std::stoi(info[2]);
        userdata["win_count"] = std::stoi(info[3]);

        /*4. 释放结果集 , 防止内存泄漏*/
        mysql_free_result(result);

        return true;
    }

    /*用户胜利 , 数据库中 : 对战场次+1 , 胜利场次+1 , 天梯分数增加 30分 */
    bool Win(uint64_t id)
    {
/*就是更新数据库的内容 - update */
#define UPDATE_WIN "update user set total_count=total_count+1, \
                                             win_count=win_count+1, \
                                             score=score+30 where id='%lu';"

        char updatebuff[4096] = {0};
        sprintf(updatebuff, UPDATE_WIN, id);

        /*加锁 , 保证多线程安全 !*/
        {
            std::unique_lock<std::mutex> lock(_mutex); // 出了作用域自动销毁

            bool ret = mysql_util::SqlExecute(_mysql, updatebuff);
            if (ret == false)
            {
                FALOG("SQL UPDATE EXECUTE ERROR !");
                return false;
            }
        }

        return true;
    }

    /*用户输了 , 数据库中 : 对战场次+1 , 天梯分数减少 30分 , 其余不变*/
    bool Lose(uint64_t id)
    {
        /*就是更新数据库的内容 - update */
#define UPDATE_LOSE "update user set total_count=total_count+1, \
                                           score=score-30 where id='%lu';"

        char updatebuff[4096] = {0};
        sprintf(updatebuff, UPDATE_LOSE, id);

        /*加锁 , 保证多线程安全 !*/
        {
            std::unique_lock<std::mutex> lock(_mutex); // 出了作用域自动销毁
            bool ret = mysql_util::SqlExecute(_mysql, updatebuff);
            if (ret == false)
            {
                FALOG("SQL UPDATE EXECUTE ERROR !");
                return false;
            }
        }

        return true;
    }

private:
    MYSQL *_mysql;     /*mysql 句柄*/
    std::mutex _mutex; /* 应对多线程同时访问数据库 , 保证线程安全 */
};

#endif

/**
 * 以上功能均经过测试 , 均可使用 !
 * 数据管理模块是对数据库的封装 , 保存用户信息 : 注册 , 登录 , 获胜 , 失败时对数据库数据的操作
 * 1. 注册 : 用户在页面注册即 , 向数据库中插入用户信息 , 这里做了密码加密 password(xx);
 * 2. 登录 : 用户在页面登录即 , 在数据库中查询该用户的信息 , 没有就无法登录 , 向用户报错
 * 3. 获胜 : 每一个用户的初始天梯分数为 : 1000分 , 获胜+30 , 场数+1(比赛总场数,获胜场数)
 * 4. 失败 : 失败的一个用户天梯分数 -30 , 总场数+1 , 其余不变
 *
 * !!!!! 特别注意 : 为什么我加了锁 ??
 *
 * 1. 数据库本身是线程安全的 , 因为数据库内部实现原子性 , 隔离性 , 一致性 , 因此数据库服务器自身能安全处理多客户端的并发访问 .
 * 2. 但是在多线程下 , 很多客户端都要访问数据库的操作句柄 , 可能共享句柄 , 对客户端而言访问数据库是线程不安全的 .
 * 3. 所以 : 我使用了锁对其优化 , 对数据库的增 , 删 , 查 , 改 操作句柄均加锁优化 , 保证多线程下的安全 , 数据完整 .
 *
 * 关于加锁 : 查操作的加锁 !
 *
 * 为什么加锁时只加锁到 mysql_store_result 完毕 , 而不对后面操作加锁 ???
 * 答 : 因为对查询而言核心的两个步骤 :
 * 1. 执行 select sql语句 , 执行完毕后会得到查询结果(但是结果在数据库中)
 * 2. 需要将数据库中查询到的结果保存到本地方便我们查看 , 所以需要 mysql_store_result 保存 , mysql_store_result只要保存成功了 , 证明数据已经到内存中了
 * 3. 而在多线程下 , 可能多个客户端同时访问查询操作 , 为了避免一个线程正在查询 , 另一个线程就进行 store 保存本地了,导致数据不一致问题(线程不安全)
 * 4. 所以 , 要对其进行加锁 , 加锁只用对两个核心步骤加锁就完全足够了 , 因为只要在加锁下完成 mysql_store_result 保存 , 此时线程一定是安全的 , 数据一定是完整的 , 内存中的数据也一定是安全并完整的 , 所以 , 后续访问内存中的数据时也一定是安全并完整的
 *
 * 关于查询 :
 * 1. 查询需要保存结果集到本地 --  MYSQL_RES * mysql_store_result
 * 2. MYSQL_RES * mysql_store_result 是在堆上申请的空间 , 最终一定要 手动 free 防止内存泄漏 !
 *                                                                       |
 *                                                                 mysql_free_result
 */